﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using Microsoft.Practices.ServiceLocation;

namespace Beaker.Injection.Desktop
{
	internal sealed class DictionaryServiceLocator
		: IServiceLocator
	{
		private readonly IReadOnlyDictionary<Type, IReadOnlyList<TypeInfo>> _types;

		public DictionaryServiceLocator(IEnumerable<IGrouping<Type, TypeInfo>> types)
		{
			var typeDictionary = new Dictionary<Type, IReadOnlyList<TypeInfo>>();

			foreach (IGrouping<Type, TypeInfo> group in types)
				typeDictionary.Add(group.Key, new ReadOnlyCollection<TypeInfo>(group.ToList()));

			_types = new ReadOnlyDictionary<Type, IReadOnlyList<TypeInfo>>(typeDictionary);
		}

		public object GetService(Type serviceType)
		{
			Contract.Requires<ArgumentNullException>(serviceType != null);

			IReadOnlyList<TypeInfo> instanceServiceType;
			if (!_types.TryGetValue(serviceType, out instanceServiceType))
				throw new ActivationException("No instance found for type " + serviceType.Name);
			if (instanceServiceType.Count > 1)
				throw new ActivationException("Multiple instances found for type " + serviceType.Name);

			return Activator.CreateInstance(instanceServiceType.Single());
		}

		public object GetInstance(Type serviceType)
		{
			return GetService(serviceType);
		}

		public object GetInstance(Type serviceType, string key)
		{
			Contract.Requires<ArgumentNullException>(string.IsNullOrEmpty(key), "No supported for namedd/keyed types.");
			return GetInstance(serviceType);
		}

		public IEnumerable<object> GetAllInstances(Type serviceType)
		{
			Contract.Requires<ArgumentNullException>(serviceType != null);

			IReadOnlyList<TypeInfo> instanceServiceType;
			if (!_types.TryGetValue(serviceType, out instanceServiceType))
				throw new ActivationException("No instance found for type " + serviceType.Name);

			return instanceServiceType.Select(Activator.CreateInstance);
		}

		public TService GetInstance<TService>()
		{
			return (TService)GetService(typeof(TService));
		}

		public TService GetInstance<TService>(string key)
		{
			Contract.Requires<ArgumentNullException>(string.IsNullOrEmpty(key), "No supported for namedd/keyed types.");
			return (TService)GetInstance(typeof(TService));
		}

		public IEnumerable<TService> GetAllInstances<TService>()
		{
			return GetAllInstances(typeof(TService)).Cast<TService>();
		}
	}
}